#pragma once
#include "../safe_handle.hpp"
#include <boost/smart_ptr.hpp>
#include <algorithm>
#include <ostream>

namespace wxx {
namespace com
{
	enum clsctx
	{
		clsctx_inproc_handler = ::CLSCTX_INPROC_HANDLER,
		clsctx_inproc_server  = ::CLSCTX_INPROC_SERVER,
		clsctx_local_server   = ::CLSCTX_LOCAL_SERVER,
		clsctx_remote_server  = ::CLSCTX_REMOTE_SERVER,

		clsctx_inproc         = clsctx_inproc_handler | 
		                        clsctx_inproc_handler,
	};

	namespace detail
	{
		template<class T>
		class _hide_AddRefRelease : public T
		{
			_hide_AddRefRelease();
			~_hide_AddRefRelease();

			STDMETHOD_(ULONG, AddRef)() = 0;
			STDMETHOD_(ULONG, Release)() = 0;
		};

		template<class T>
		struct com_ptr_default_factory
		{
			static T* CreateInstance(const ::CLSID& clsid, enum clsctx clsctx)
			{
				T* ptr;

				ensure_success_hr(
					::CoCreateInstance(clsid, 0, clsctx, *IID, reinterpret_cast<void**>(&ptr))
				);

				return ptr;
			}
		};
	}

	template<class T, const ::IID* = &__uuidof(T), class Factory = detail::com_ptr_default_factory<T> >
	struct com_ptr	
	{
		typedef T element_type;
		typedef T value_type;
		typedef T* pointer;
		typedef T& reference;
		typedef detail::_hide_AddRefRelease<T>* call_pointer;

		explicit com_ptr(T* p = 0)
			: ptr(p)
		{
		}

		explicit com_ptr(const com_ptr& other)
			: ptr(other.ptr)
		{
			if (ptr) ptr->AddRef();
		}

		explicit com_ptr(const ::CLSID& clsid, enum clsctx clsctx = clsctx_inproc)
			: ptr(0)
		{
			create(clsid, clsctx);
		}

		~com_ptr()
		{		
			if (ptr) ptr->Release();
		}

		void reset()
		{
			attach(0);
		}

		void create(const ::CLSID& clsid, enum clsctx clsctx = clsctx_inproc)
		{
			attach(Factory::CreateInstance(clsid, clsctx));
		}

		void swap(com_ptr & other)
		{
			if (other.ptr != ptr)
			{
				std::swap(ptr, other.ptr);
			}
		}

		void attach(pointer p)
		{
			if (ptr) 
			{
				ptr->Release();
			}

			ptr = p;
		}

		pointer detach()
		{
			pointer p = ptr;
			ptr = 0;

			return p;
		}

		com_ptr& operator=(const com_ptr& rhs)
		{
			if (ptr != rhs.ptr)
			{
				if (rhs.ptr) 
					rhs.ptr->AddRef();

				if (ptr)
					ptr->Release();
			}

			return *this;
		}

		com_ptr& operator=(T* rhs)
		{
			attach(rhs);
			return *this;
		}

		operator pointer() const throw()
		{
			return ptr;
		}

		call_pointer operator->() const throw()
		{
			return reinterpret_cast<call_pointer>(ptr);
		}

		reference operator*() const throw(...)
		{
			return *ptr;
		}
		
		bool operator!() const throw()
		{
			return !ptr;
		}

	private:
		pointer ptr;
	};


}}